package io.gitee.zhangbinhub.acp.cloud.resource.server

import cn.dev33.satoken.context.SaHolder
import cn.dev33.satoken.exception.NotLoginException
import cn.dev33.satoken.exception.SaTokenException
import cn.dev33.satoken.`fun`.SaFunction
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckAccessTokenHandler
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientIdSecretHandler
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientTokenHandler
import cn.dev33.satoken.oauth2.template.SaOAuth2Util
import cn.dev33.satoken.reactor.filter.SaReactorFilter
import cn.dev33.satoken.router.SaHttpMethod
import cn.dev33.satoken.router.SaRouter
import cn.dev33.satoken.stp.StpUtil
import cn.dev33.satoken.strategy.SaAnnotationStrategy
import cn.dev33.satoken.strategy.SaStrategy
import io.gitee.zhangbinhub.acp.boot.conf.AcpCorsConfiguration
import io.gitee.zhangbinhub.acp.cloud.resource.server.conf.AcpCloudResourceServerConfiguration
import io.gitee.zhangbinhub.acp.cloud.resource.server.constant.AcpCloudResourceServerConstant
import io.gitee.zhangbinhub.acp.cloud.resource.server.tools.TokenTools
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.acp.core.common.log.LogFactory
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.web.ServerProperties
import org.springframework.context.annotation.Bean
import org.springframework.core.annotation.Order

/**
 * Oauth2 资源服务配置
 *
 * @since JDK 17
 */
@AutoConfiguration(after = [AcpCloudResourceServerComponentAutoConfiguration::class])
class AcpCloudResourceServerReactiveAutoConfiguration(
    private val acpCorsConfiguration: AcpCorsConfiguration,
    private val acpCloudResourceServerConfiguration: AcpCloudResourceServerConfiguration,
    private val acpCloudResourceServerComponentAutoConfiguration: AcpCloudResourceServerComponentAutoConfiguration,
    serverProperties: ServerProperties,
    private val webEndpointProperties: WebEndpointProperties
) {
    private val log = LogFactory.getInstance(this.javaClass)
    private val contextPath: String =
        if (CommonTools.isNullStr(serverProperties.servlet.contextPath)) "" else serverProperties.servlet.contextPath

    /**
     * 注册 [Sa-Token 全局过滤器]
     */
    @Bean
    @Order(AcpCloudResourceServerConstant.RESOURCE_SERVER_SECURITY_FILTER_CHAIN_ORDER)
    fun saReactorFilter(): SaReactorFilter = SaReactorFilter().apply {
        acpCloudResourceServerComponentAutoConfiguration.permitAllPath()
            .forEach { uri -> log.info("permitAll uri: $uri") }
        log.info("security uri: other any")
        // 指定 [拦截路由] 与 [放行路由]
        this.addInclude(ALL_PATH)
        this.setAuth {
            SaRouter.notMatch(SaHttpMethod.OPTIONS)
                .match("$contextPath${webEndpointProperties.basePath}")
                .match("$contextPath${webEndpointProperties.basePath}$ALL_PATH").check(SaFunction {
                    SaHttpBasicUtil.check("${acpCloudResourceServerConfiguration.clientId}:${acpCloudResourceServerConfiguration.clientSecret}")
                })
            SaRouter.notMatch(SaHttpMethod.OPTIONS)
                .notMatch("$contextPath${webEndpointProperties.basePath}")
                .notMatch("$contextPath${webEndpointProperties.basePath}$ALL_PATH")
                .notMatch(acpCloudResourceServerComponentAutoConfiguration.permitAllPath())
                .notMatch(acpCloudResourceServerComponentAutoConfiguration.permitOauthApi())
                .match(ALL_PATH, SaFunction {
                    val accessToken = TokenTools.getAccessTokenModel()
                    val clientToken = TokenTools.getClientTokenModel()
                    if (accessToken != null) {
                        SaOAuth2Util.checkAccessToken(accessToken.accessToken)
                        try {
                            TokenTools.getAccessTokenModel()?.also { accessToken ->
                                StpUtil.getStpLogic().setTokenValueToStorage(
                                    StpUtil.getTokenValueByLoginId(
                                        accessToken.getLoginId(),
                                        accessToken.clientId
                                    )
                                )
                            }
                            StpUtil.checkLogin()
                        } catch (e: NotLoginException) {
                            throw NotLoginException("token 无效", "", "-2").setCode(11012)
                        }
                    }
                    if (clientToken != null) {
                        SaOAuth2Util.checkClientToken(clientToken.clientToken)
                    }
                    if (accessToken == null && clientToken == null) {
                        throw NotLoginException("token 无效", "", "-2").setCode(11012)
                    }
                })
        }
        this.setBeforeAuth {
            SaHolder.getResponse()
                // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                .setHeader("X-Frame-Options", "SAMEORIGIN")
                // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                .setHeader("X-XSS-Protection", "1; mode=block")
                // 禁用浏览器内容嗅探
                .setHeader("X-Content-Type-Options", "nosniff")
        }
        this.setError { e ->
            if (e is SaTokenException) {
                throw e
            } else {
                throw SaTokenException(e)
            }
        }
    }.also {
        SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckAccessTokenHandler())
        SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckClientIdSecretHandler())
        SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckClientTokenHandler())
        // 跨域处理
        if (acpCorsConfiguration.enabled) {
            SaStrategy.instance.corsHandle =
                acpCloudResourceServerComponentAutoConfiguration.saCorsHandleFunction(acpCorsConfiguration)
        }
    }

    companion object {
        private const val ALL_PATH = "/**"
    }
}
